<!doctype html>
<html>
  <head>
    <title>Test onended with Different Playback Rates</title>
    <style type="text/css">
      header {
        margin: 20px 0;
      }
      #results {
        white-space: pre;
        font-family: monospace;
      }
    </style>
  </head>

  <body>
    <h1>Test onended with Different Playback Rates</h1>

    <p>
      Run the following tests in order.  In all cases the onended event should be fired.

      See <a href="crbug.com/484935">crbug.com/484935</a>.
    </p>
    <ol>
      <li>
        Press "Rate 1" button to test onended with a playback rate of 1. You should hear a sound and
        the onended event should be fired, which should print a message to the console and the
        Result are below.  This audio is the reference.
        <p>
          The onended event should occur after about <span id="rate-1-duration"></span> sec.
        </p>
      </li>
      <li>
        Press "Rate 2" button to do the same test but with the playback rate set to 2. This should
        sound somewhat like 1, but have a higher pitch and finish sooner.
        <p>
          The onended event should occur after about <span id="rate-2-duration"></span> sec.
        </p>
      </li>
      <li>
        Press "Rate 1/2" button to do the same test but with the playback rate set to 1/2. This
        should sound like 1, but last twice as long with a lower pitch.  The entire audio sample
        should be played. (Compare with Rate 1.)

        <p>
          The onended event should occur after about <span id="rate-half-duration"></span> sec.
        </p>
      </li>
      <li>
        Press "Rate variable" button to test playback with a variable playback rate.  The entire
        sample should be played.  Make sure this is distantly different from the other rates
        above. If not, then playback automation did not work and a new bug should be filed.

        <p>
          The onended event should occur after approximately <span
          id="rate-var-min-duration"></span> sec, but may take longer.
        </p>
      </li>
    </ol>
    <button id="rate-1" disabled onclick="test1()">Rate 1</button>
    <button id="rate-2" disabled onclick="test2()">Rate 2</button>
    <button id="rate-half" disabled onclick="testHalf()">Rate 1/2</button>
    <button id="rate-var" disabled onclick="testVariable()">Rate variable</button>

    <header>Results</header>
    <div id="results"></div>

    <script>
      var context = new AudioContext();
      var src;
      var buffer;
      // Duration (in sec) of the sine source to be used as the test signal.
      var duration = 1;

      function generateTestSignal () {
        // Create a new test signal.  A tone of nominal length |duration|.  Near the end, we
        // increase the amplitude and then finally fade out the signal.  We do this so that we can
        // hear when the tone should be ending in case a test fails and the output is clipped
        // permaturely.

        // Time from the nominal end where we increase the amplitude
        var changeTime = 0.2;
        // How fast to fade out the signal.
        var timeConstant = 0.2;

        // Create an offline context long enough to hold the nominal tone plus the fade out.
        // Somewhat arbitrarily use 5 time constants as the duration of the fade.  The signal should
        // be small enough that there's not large glitch at the end, but short enough that we don't
        // have a long silence at the end.
        var contextDuration = duration + timeConstant * 5;
        var offline = new OfflineAudioContext(1, contextDuration * context.sampleRate, context.sampleRate);

        var osc = offline.createOscillator();
        var gain = offline.createGain();

        osc.connect(gain);
        gain.connect(offline.destination);

        // Start the tone at amplitude 0.75.
        gain.gain.setValueAtTime(.75, 0);
        // Gradually increase the gain to 1, a little before the nominal end of the tone.
        gain.gain.setTargetAtTime(1, duration - changeTime, timeConstant);
        // Now fade out the signal.
        gain.gain.setTargetAtTime(0, duration, timeConstant);
        osc.start();

        offline.startRendering().then(function (b) {
          buffer = context.createBuffer(1, b.length, context.sampleRate);
          buffer.copyToChannel(b.getChannelData(0), 0);

          // Inform user how long the done is.
          log("Test signal duration = " + buffer.duration + " sec");

          // Update the text with the actual lengths so we can compare the expected onended time and
          // the actual.
          document.getElementById("rate-1-duration").textContent = contextDuration;
          document.getElementById("rate-2-duration").textContent = contextDuration / 2;
          document.getElementById("rate-half-duration").textContent = contextDuration / 0.5;

          // The factor 1.13 is an approximation of where the onended event should occur. This was
          // determined by experimentation because it's pretty hard to calculate the actual duration
          // when we automate the playback rate in complicated ways.
          document.getElementById("rate-var-min-duration").textContent = 1.13 * contextDuration;

          // Signal generated so we can enable the buttons now.
          enableButtons();
        });
      }

      function enableButtons () {
        document.getElementById("rate-1").disabled = false;
        document.getElementById("rate-2").disabled = false;
        document.getElementById("rate-half").disabled = false;
        document.getElementById("rate-var").disabled = false;
      }

      window.onload = generateTestSignal;

      function createGraph(rate) {
        // Create a simple graph with the source connected to the destination and set up the
        // playback rate according to |rate|.

        src = context.createBufferSource();
        src.buffer = buffer;
        src.playbackRate.value = rate;
        src.connect(context.destination);
      }

      function test1() {
        // Rate 1 test.
        createGraph(1);
        var startTime = context.currentTime;
        src.onended = function () {
          log("Rate 1 test ended at " + (context.currentTime - startTime) + " sec");
        }
        src.start();
      }

      function test2() {
        // Rate 2 test
        createGraph(2);
        var startTime = context.currentTime;
        src.onended = function () {
          log("Rate 2 test ended at " + (context.currentTime - startTime) + " sec");
        }
        src.start();
      }

      function testHalf() {
        // Rate 1/2 test
        createGraph(0.5);
        var startTime = context.currentTime;
        src.onended = function () {
          log("Rate 0.5 test ended at " + (context.currentTime - startTime) + " sec");
        }
        src.start();
      }

      function testVariable() {
        // Variable rate test.  Set the nominal playback rate to 0 so that the automation completely
        // determines the playback rate. (Otherwise it gets added to the intrinsic playback rate.)
        createGraph(0);

        // Create a constant buffer of value 1 that we will automate to generate the desired
        // playback rate.

        var gainSrc = context.createBufferSource();
        var gainSrcBuffer = context.createBuffer(1, 1, context.sampleRate);
        var d = gainSrcBuffer.getChannelData(0);
        d[0] = 1;
        gainSrc.buffer = gainSrcBuffer;
        gainSrc.loop = true;

        // Automate this gain node to produce the desired playback rate.  What we want is to start
        // the playback rate at 2, exponentially ramp down to 0.1 at |duration|/2.  Then linear ramp
        // back up to 1.  This will cause the output signal to change pitch and duration.  The exact
        // modulation is not important except that we want the minimum playback rate to be less than
        // 1 to make sure we don't prematurely end the sample causing the onended event not to be
        // fired.
        var playback = context.createGain();
        playback.gain.setValueAtTime(2, context.currentTime);
        playback.gain.exponentialRampToValueAtTime(0.1, context.currentTime + duration / 2);
        playback.gain.linearRampToValueAtTime(1, context.currentTime + duration);

        gainSrc.connect(playback);
        playback.connect(src.playbackRate);

        gainSrc.start();

        var startTime = context.currentTime;
        src.onended = function () {
          log("Rate variable test ended at " + (context.currentTime - startTime) + " sec");
        }

        src.start();
      }

      function log(message) {
        console.log(message);
        var results = document.getElementById("results");
        results.textContent += message + "\n";
      }
    </script>
  </body>
</html>
